/// @file Cloth.cpp
/// @brief the main cloth that creates, and manages the internal structure of the cloth

#include "math.h"

#include "QTime"
#include "QImage"

#include "Cloth.h"
#include "Configuration.h"
#include "Utilities.h"

Cloth::Cloth
        (
            const int _height,
            const int _width,
            const int _verticalParticleCount,
            const int _horizontalParticleCount,
            const float _mass
        )
{
    //set scenario
    m_scenario = TABLECLOTH;

    //init params
    m_drawWireframe = false;

    //init weighted cloth params
    m_maxLastRowWeight = 5;
    m_weightSteps = 0;

    //init particles params
    m_particleMass = _mass;
    initialise(_height, _width, _verticalParticleCount, _horizontalParticleCount, 0.007, EULAR_FORWARD);

    //read cloth materials from configuration (texture, stiffness, damping)
    m_clothMaterialList = Configuration::initialiseClothMaterials();
    m_currentClothMaterial = getDefaultClothMaterial();

    //init textures
    m_textureCount = m_clothMaterialList->size();
    m_textureWidth = 0;
    m_textureHeight = 0;
    for (int i = 0; i < m_textureCount; i++) m_textureHandler.push_back(0);

    //init forces
    m_enableGravity = true;
    m_enableHookeBend = true;
    m_enableHookeStretch = true;
    m_enableHookeStructural = true;

    //trace particles params
    m_enableInternalTrace = true;
    m_internalTraceParticleId = 0;
    m_enableInternalTraceNeighbours = true;

    //automatic update init
    m_enableAutoCloth = false;

    //init wind params
    m_windEnabled = false;
    m_windStrength = 0.005;
    m_windDirection.set(0, 0, 0);

    //init particle size
    m_particleRadius = 4.0;
}

Cloth::~Cloth()
{
    //clean up
    std::cout << "Cloth Cleanup" << std::endl;

    //delete all particle objects in the list and clear the list
    deleteParticles();

    //delete cloth materials
    for (int i = 0; i < m_clothMaterialList->size(); i++) delete (*m_clothMaterialList)[i];
    delete m_clothMaterialList;

    std::cout << "Cloth deleted\n" << std::endl;
}

void Cloth::deleteParticles()
{
    //delete quad list
    delete m_particleInQuadList;

    //delete springs
    for (int i = 0; i < m_springList->size(); i++) delete (*m_springList)[i];
    delete m_springList;

    //delete particles
    for (int i = 0; i < m_particleList->size(); i++) delete (*m_particleList)[i];
    delete m_particleList;
}

void Cloth::reInitialiseParticles
                        (
                            const int _height,
                            const int _width,
                            const int _verticalParticleCount,
                            const int _horizontalParticleCount,
                            const float _timestep,
                            const IntegrationType _integrationType,
                            const ConstrainedScenario _scenario
                        )
{
    //update scenario
    m_scenario = _scenario;

    //calls normal reinit routine
    reInitialiseParticles(_height, _width, _verticalParticleCount, _horizontalParticleCount, _timestep, _integrationType);
}


void Cloth::reInitialiseParticles
                        (
                            const int _height,
                            const int _width,
                            const int _verticalParticleCount,
                            const int _horizontalParticleCount,
                            const float _timestep,
                            const IntegrationType _integrationType
                        )
{
    //delete all particles
    deleteParticles();

    //reinitialise particles
    initialise(_height, _width, _verticalParticleCount, _horizontalParticleCount, _timestep, _integrationType);
}

void Cloth::initialise
                (
                    const int _height,
                    const int _width,
                    const int _verticalParticleCount,
                    const int _horizontalParticleCount,
                    const float _timestep,
                    const IntegrationType _integrationType
                )
{
    m_height = _height;
    m_width = _width;
    m_verticalParticleCount = _verticalParticleCount;
    m_horizontalParticleCount = _horizontalParticleCount;

    m_initialClothHeight = 20;

    //init timestep
    m_timestep = _timestep;

    //init integration method
    m_integrationType = _integrationType;

    //create the mass-spring particles
    createAndConnectParticles();
}

void Cloth::createAndConnectParticles()
{
    //create particles
    createParticles();

    //position particles - initial orientation depends on scenario
    if (m_scenario == FLAG || m_scenario == BANNER)
        positionParticles(false);
    else
        //curtain, tablecloth and shelter -> horizontal
        positionParticles();

    //connect particles together-> create springs
    connectParticles();

    //init default pinnings
    initialStaticConstraints();
}

void Cloth::createParticles()
{
    //initialise particle list
    m_particleList = new std::vector<Particle*>;

    //initialise spring list
    m_springList = new std::vector<Spring*>;

    //initialise particle quad list
    m_particleQuadPositionList.clear();
    m_particleInQuadList = new std::vector<Particle*>;
    m_textureCoords.clear();

    //initialise particle position list
    m_particlePositionList.clear();

    for (int z = 0; z < m_verticalParticleCount; z++)
    {
        for (int x = 0; x < m_horizontalParticleCount; x++)
        {
            //determine id of next particle to create
            int id = getIdFromCoordinates(z, x);

            //create a particle
            Particle* p = new Particle(id, m_particleMass);

            //add particle to list of particles
            m_particleList->push_back(p);

            //add an empty placeholder to the position list
            m_particlePositionList.push_back(ngl::Vector(0, 0, 0));

            if (Configuration::s_debug) std::cout << "Cloth Particle created with id = " << id << std::endl;
        }
    }

    //setup quad list for drawing solid shaded and wind calculation
    setupQuadsForSolidDrawing();

    //Add differential weights at the end of the cloth
    //solve the problem of tip curling at the end - make the last row heavier!
    int i = m_weightSteps;
    for (int z = m_verticalParticleCount - m_weightSteps; z < m_verticalParticleCount; z++, i--)
    {
        for (int x = 0; x < m_horizontalParticleCount; x++)
        {
            int id = getIdFromCoordinates(z, x);
            Particle* p = (*m_particleList)[id];
            p->setMass(p->getMass() * m_maxLastRowWeight / i);
        }
    }
}

void Cloth::setupQuadsForSolidDrawing()
{
    //calculate starting index of last row (cannot apply quad calculation there!)
    int lastRowStartIndex = m_horizontalParticleCount * (m_verticalParticleCount - 1);

    //add quad particles to the vextex array
    for (int i = 0; i < m_particleList->size(); i++)
    {
        if ((i % m_horizontalParticleCount) == 0)
        {
            //we are on the left side -> skip
            continue;
        }

        if (i >= lastRowStartIndex)
        {
            //we are on the last row -> skip
            continue;
        }

        //safe to add particles
        m_particleInQuadList->push_back((*m_particleList)[i]);
        m_particleInQuadList->push_back((*m_particleList)[i - 1]);
        m_particleInQuadList->push_back((*m_particleList)[(i - 1) + m_horizontalParticleCount]);
        m_particleInQuadList->push_back((*m_particleList)[i + m_horizontalParticleCount]);

        //add an empty placeholder to the quadposition list
        m_particleQuadPositionList.push_back(ngl::Vector(0, 0, 0));
        m_particleQuadPositionList.push_back(ngl::Vector(0, 0, 0));
        m_particleQuadPositionList.push_back(ngl::Vector(0, 0, 0));
        m_particleQuadPositionList.push_back(ngl::Vector(0, 0, 0));
    }

    //setup texture coordinates for quads
    Particle* currentParticle = 0;
    for (int i = 0; i < m_particleInQuadList->size(); i++)
    {
        //get particle
        currentParticle = (*m_particleInQuadList)[i];

        //get id
        int id = currentParticle->getId();

        //derive (z, x) from id
        int z = 0; int x = 0;
        getZXCoordinatesFromId(id, z, x);

        //fit z in unit vertical space
        float zt = (float)z / (float)(m_verticalParticleCount - 1);

        //fit x in unit horizontal space
        float xt = (float)x / (float)(m_horizontalParticleCount - 1);

        //add coordinates to texturecoord list
        m_textureCoords.push_back(zt); m_textureCoords.push_back(xt);
    }
}

void Cloth::positionParticles(const bool _isHorizontal)
{
    //position particles to fill in the 2d space
    m_xStep = (m_horizontalParticleCount - 1) == 0 ? 0 : (float)m_width / (float)(m_horizontalParticleCount - 1);
    m_zStep = (m_verticalParticleCount - 1) == 0 ? 0 : (float)m_height / (float)(m_verticalParticleCount - 1);

    float xStartPos = m_xStep == 0 ? 0 : (float)m_width / 2.0;
    float zStartPos = m_zStep == 0 ? 0 : (float)m_height / 2.0;

    for (int z = 0; z < m_verticalParticleCount; z++)
    {
        for (int x = 0; x < m_horizontalParticleCount; x++)
        {
            //determine id of current particle
            int id = getIdFromCoordinates(z, x);

            //get particle from list
            Particle* p = (*m_particleList)[id];

            //set position of particle
            if (_isHorizontal)
                p->setInitialPosition(ngl::Vector(xStartPos - (x * m_xStep), m_initialClothHeight, zStartPos - (z * m_zStep)));
            else
                p->setInitialPosition(ngl::Vector(xStartPos - (x * m_xStep), (m_initialClothHeight/2) + (zStartPos - (z * m_zStep)), zStartPos));

            p->setPosition(p->getInitialPosition());
        }
    }
}

void Cloth::connectParticles()
{
    //calculate rest lengths
    m_structuralHorizontalRestLength = m_xStep;
    m_structuralVerticalRestLength = m_zStep;
    m_shearRestLength = sqrt((m_xStep * m_xStep) + (m_zStep * m_zStep));
    m_flexHorizontalRestLength = m_xStep * 2;
    m_flexVerticalRestLength = m_zStep * 2;

    //create springs to connect particles with their neighbours for structural, shear and flexion
    for (int z = 0; z < m_verticalParticleCount; z++)
    {
        for (int x = 0; x < m_horizontalParticleCount; x++)
        {
            //determine id of current particle
            int mainId = getIdFromCoordinates(z, x);

            //evaluate vertical structural force
            if (z < m_verticalParticleCount - 1)
            {
                //get node to the bottom of it
                int neighbourId = mainId + m_horizontalParticleCount;

                //create spring
                createGlobalSpring(mainId, neighbourId, m_structuralVerticalRestLength, STRUCTURAL_SPRING);
            }

            //evaluate horizontal structural force
            if (x < m_horizontalParticleCount - 1)
            {
                //get node to the right of it
                int neighbourId = mainId + 1;

                //create spring
                createGlobalSpring(mainId, neighbourId, m_structuralHorizontalRestLength, STRUCTURAL_SPRING);
            }

            //evaluate top-right shear force
            if ((z > 0) && (x < m_horizontalParticleCount - 1))
            {
                //get node to the top-right of it
                int neighbourId = getIdFromCoordinates(z - 1, x + 1);

                //create spring
                createGlobalSpring(mainId, neighbourId, m_shearRestLength, SHEAR_SPRING);
            }

            //evaluate bottom-right shear force
            if ((z < m_verticalParticleCount - 1) && (x < m_horizontalParticleCount - 1))
            {
                //get node to the bottom-right of it
                int neighbourId = getIdFromCoordinates(z + 1, x + 1);

                //create spring
                createGlobalSpring(mainId, neighbourId, m_shearRestLength, SHEAR_SPRING);
            }

            //evaluate vertical flex force
            if (z < m_verticalParticleCount - 2)
            {
                //get node to the bottom of it
                int neighbourId = mainId + (m_horizontalParticleCount * 2);

                //create spring
                createGlobalSpring(mainId, neighbourId, m_flexVerticalRestLength, FLEX_SPRING);
            }

            //evaluate horizontal structural force
            if (x < m_horizontalParticleCount - 2)
            {
                //get node to the right of it
                int neighbourId = mainId + 2;

                //create spring
                createGlobalSpring(mainId, neighbourId, m_flexHorizontalRestLength, FLEX_SPRING);
            }
        }
    }

    //refresh the neighbour list of each particles for quicker access to their neighbours
    //without having to search through the global neighbour list again
    refreshParticleNeighbours();
}

void Cloth::createGlobalSpring
                            (
                                const int _mainId,
                                const int _neighbourId,
                                const float _length,
                                const SpringType _springType
                            )
{
    //get main particle object
    Particle* mainParticle = (*m_particleList)[_mainId];

    //get neighbouring particle
    Particle* neighbourParticle = (*m_particleList)[_neighbourId];

    //create a new spring to connect the 2 particles
    Spring* spring = new Spring(mainParticle, neighbourParticle, _length, _springType);

    //add the new spring to the global spring list
    m_springList->push_back(spring);
}

void Cloth::refreshParticleNeighbours()
{
    for (int i = 0; i < m_springList->size(); i++)
    {
        //get current spring
        Spring* currentSpring = (*m_springList)[i];

        //clone forward spring
        Spring* forward = new Spring(currentSpring->getRootNode(), currentSpring->getEndNode(), currentSpring->getRestLength(), currentSpring->getSpringType());

        //add to neighbour list
        Particle* rootParticle = (*m_particleList)[currentSpring->getRootNode()->getId()];
        rootParticle->getNeighbourList()->push_back(forward);

        //create backward spring
        Spring* backward = new Spring(currentSpring->getEndNode(), currentSpring->getRootNode(), currentSpring->getRestLength(), currentSpring->getSpringType());

        //add to neighbour list
        Particle* endParticle = (*m_particleList)[currentSpring->getEndNode()->getId()];
        endParticle->getNeighbourList()->push_back(backward);
    }
}

void Cloth::initialStaticConstraints()
{
    //unpin everything to start with
    pinAll(false);

    if (m_scenario == TABLECLOTH)
    {
        //unpin all
        pinAll(false);
    }
    else if (m_scenario == LIGHT_CURTAIN)
    {
        //pin down 2 topmost corner particles to be used as hanging contraints
        Particle* p = (*m_particleList)[0];
        p->setMoveable(false);

        p = (*m_particleList)[m_horizontalParticleCount - 1];
        p->setMoveable(false);
    }
    else if (m_scenario == SOLID_CURTAIN)
    {
        //pin first row
        pinRow(true, 0);
    }
    else if (m_scenario == SHELTER)
    {
        //pin first and last row
        pinRow(true, 0);
        pinRow(true, m_verticalParticleCount - 1);
    }
    else if (m_scenario == FLAG)
    {
        //pin down 2 leftmost corner particles to be used as hanging contraints
        Particle* p = (*m_particleList)[0];
        p->setMoveable(false);

        p = (*m_particleList)[getIdFromCoordinates(m_verticalParticleCount - 1, 0)];
        p->setMoveable(false);
    }
    else if (m_scenario == BANNER)
    {
        //pin first and last column
        pinColumn(true, 0);
        pinColumn(true, m_horizontalParticleCount - 1);
    }
}

void Cloth::loadGeometry()
{
    //create vbo for sphere
    ngl::VBOPrimitives* prim = ngl::VBOPrimitives::instance();
    prim->createVBOSphere("sphere", 0.2, 16);

    //create textures for solid shading
    createTextures();

}

void Cloth::draw
                    (
                        ngl::TransformStack _txStack,
                        ShaderManager* io_sman,
                        const std::string _wireframeShader,
                        const std::string _textureShader
                    )
{
    //if not wireframe enabled, draw solid shaded quad
    if (!m_drawWireframe)
    {
        //add positions to the vextex array
        Particle* p = 0;
        for (int i = 0; i < m_particleInQuadList->size(); i++)
        {
            p = (*m_particleInQuadList)[i];

            m_particleQuadPositionList[i] = p->getPosition();
        }

        _txStack.pushTransform();
        {
            //update transform
            io_sman->updateModel(_textureShader, _txStack.getCurrAndGlobal().getMatrix());

            glEnableClientState(GL_VERTEX_ARRAY);
            glVertexPointer(4, GL_FLOAT, 0, &m_particleQuadPositionList[0]);

            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
            glTexCoordPointer(2, GL_FLOAT, 0, &m_textureCoords[0]);

            glBindTexture(GL_TEXTURE_2D, m_textureHandler[m_currentClothMaterialIndex]);

            for (int i = 0; i < m_particleQuadPositionList.size(); i += 4)
            {
                glDrawArrays(GL_QUADS, i, 4);
            }

            glDisableClientState(GL_VERTEX_ARRAY);
            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        }
        _txStack.popTransform();
    }
    else
    {
        //add positions to the vextex array
        Particle* p = 0;
        for (int i = 0; i < m_particleList->size(); i++)
        {
            p = (*m_particleList)[i];

            m_particlePositionList[i] = p->getPosition();
        }

        _txStack.pushTransform();
        {
            //update transform
            io_sman->updateModel(_wireframeShader, _txStack.getCurrAndGlobal().getMatrix());

            glEnableClientState(GL_VERTEX_ARRAY);
            glVertexPointer(4, GL_FLOAT, 0, &m_particlePositionList[0]);

            glPointSize(m_particleRadius);

            for (int i = 0; i < m_particlePositionList.size(); i++)
            {
                io_sman->updateColor(_wireframeShader, determineParticleColor((*m_particleList)[i]), false);

                glDrawArrays(GL_POINTS, i, 1);
            }

            glDisableClientState(GL_VERTEX_ARRAY);
        }
        _txStack.popTransform();
    }

    //draw pins at fixed points
    Particle* currentParticle = 0;
    for (int i = 0; i < m_particleList->size(); i++)
    {
        currentParticle = (*m_particleList)[i];

        if (!currentParticle->getMoveable())
        {
            //draw sphere at position

            _txStack.pushTransform();
            {
                //update shader
                _txStack.setPosition(currentParticle->getPosition().m_x, currentParticle->getPosition().m_y, currentParticle->getPosition().m_z);
                _txStack.setScale(0.4, 0.4, 0.4);
                io_sman->updateModelColor("Light", _txStack.getCurrAndGlobal().getMatrix(), ngl::Colour(1, 1, 0));

                ngl::VBOPrimitives *prim=ngl::VBOPrimitives::instance();
                prim->draw("cube");
            }
            _txStack.popTransform();
        }
    }
}

ngl::Colour Cloth::determineParticleColor(const Particle* _particle)
{
    //initialise to default normal particle colour
    ngl::Colour finalColour(0, 0, 0);
    finalColour.set(Configuration::s_normalColour);

    if (!m_enableInternalTrace)
    {
        //no tracing -> display normal colour
        return finalColour;
    }

    //we are tracing -> check if we are the main particle
    if (m_internalTraceParticleId == _particle->getId())
    {
        //thats us -> display main trace colour
        finalColour.set(Configuration::s_mainTraceColour);
        return finalColour;
    }

    //we are tracing and we are not the main particle
    if (m_enableInternalTraceNeighbours)
    {
        //we are tracing neighbours also -> lets check if we are among neighbours

        finalColour.set(determineIfIsANeighbourAndGetColour(m_internalTraceParticleId, _particle->getId()));
        return finalColour;
    }

    //maybe no match -> safety return
    return finalColour;
}

ngl::Colour Cloth::determineIfIsANeighbourAndGetColour
                                (
                                    const int _mainId,
                                    const int _testId
                                )
{
    //get neighbours of mainId
    Particle* mainParticle = (*m_particleList)[_mainId];
    std::vector<Spring*>* neighbours = mainParticle->getNeighbourList();

    ngl::Colour finalColour(Configuration::s_normalColour);
    Spring* spring = 0;
    for (int i = 0; i < neighbours->size(); i++)
    {
        spring = (*neighbours)[i];
        if (spring->getEndNode()->getId() == _testId)
        {
            //set neighbour trace colour
            finalColour.set(Configuration::s_neighbourTraceColour);

            //set colour based on spring type
            if (spring->getSpringType() == STRUCTURAL_SPRING)
                finalColour.set(Configuration::s_structuralNeighbourTraceColour);
            else if (spring->getSpringType() == SHEAR_SPRING)
                finalColour.set(Configuration::s_shearNeighbourTraceColour);
            else if (spring->getSpringType() == FLEX_SPRING)
                finalColour.set(Configuration::s_flexNeighbourTraceColour);

            break;
        }
    }

    return finalColour;
}

void Cloth::updateParticles(ObstacleManager *_obstacleManager)
{
    //evaluate net forces on each particle
    evaluateNetForces();

    //loop over the list of particles
    Particle* currentParticle = 0;
    for (int i = 0; i < m_particleList->size(); i++)
    {
        currentParticle = (*m_particleList)[i];

        if (currentParticle->getMoveable())
        {
            //determine next (position, velocity) of particle
            integrateNext(currentParticle);

            //check and resolve collision
            ngl::Vector newPosition = 0;
            if (isCollisionDetected(_obstacleManager, currentParticle, newPosition))
            {
                //readjust (position, velocity) of particle wrt to corrective new positive
                integrateBackward(currentParticle, newPosition);
            }
        }
    }
}

void Cloth::integrateNext(Particle* currentParticle)
{
    //calls user-chosen integrator
    switch (m_integrationType)
    {
        case EULAR_FORWARD: { evaluateEularExplicit(currentParticle); break; }
        case VERLET: { evaluateVerlet(currentParticle); break; }

        default : break;
    }
}

void Cloth::integrateBackward(Particle* currentParticle, ngl::Vector newPosition)
{
    //move to new position
    currentParticle->setCurrentPosition(newPosition);

    //resolve velocity
    if (m_integrationType == EULAR_FORWARD)
    {
        //need to find new velocity to move from originating position to new position
        currentParticle->setCurrentVelocity((newPosition - currentParticle->getLastPosition()) / m_timestep);
    }
    else if (m_integrationType == VERLET)
    {
        //derive current velocity
        ngl::Vector velocity = (newPosition - currentParticle->getLastLastPosition()) / (2 * m_timestep);
        currentParticle->setCurrentVelocity(velocity);
    }
}

void Cloth::evaluateNetForces()
{
    //apply gravity to each particle
    Particle* currentParticle = 0;
    for (int i = 0; i < m_particleList->size(); i++)
    {
        //evaluate net force of neighbours on current particle
        currentParticle = (*m_particleList)[i];

        //clear net force
        currentParticle->clearNetForce();

        //adds in gravity
        if (m_enableGravity) currentParticle->addToNetForce(new ngl::Vector(0, currentParticle->getMass() * Configuration::s_accelerationOfFreeFall, 0));
    }

    //apply wind to each triangle
    if (m_windEnabled)
    {
        for (int i = 0; i < m_particleInQuadList->size(); i += 4)
        {
            //for example, consider the following grid
            //0 1 2
            //3 4 5
            //quad list : 1 0 3 4 # 2 1 4 5
            //triangles in quad 1 = 1 0 3 # 4 1 3
            //triangles in quad 2 = 2 1 4 # 5 2 4
            addWindForceOnTriangle((*m_particleInQuadList)[i], (*m_particleInQuadList)[i+1], (*m_particleInQuadList)[i+2], m_windDirection);
            addWindForceOnTriangle((*m_particleInQuadList)[i+3], (*m_particleInQuadList)[i], (*m_particleInQuadList)[i+2], m_windDirection);
        }
    }

    //apply spring force/hooke's law to each spring
    for (int i = 0; i < m_springList->size(); i++)
    {
        //get current spring
        Spring* currentSpring = (*m_springList)[i];

        if ((!m_enableHookeStructural) && (currentSpring->getSpringType() == STRUCTURAL_SPRING)) continue;
        if ((!m_enableHookeStretch) && (currentSpring->getSpringType() == SHEAR_SPRING)) continue;
        if ((!m_enableHookeBend) && (currentSpring->getSpringType() == FLEX_SPRING)) continue;

        //calculate spring force
        determineInternalSpringForce(currentSpring);
    }
}

void Cloth::addWindForceOnTriangle
                            (
                                Particle* p1,
                                Particle* p2,
                                Particle* p3,
                                const ngl::Vector _direction
                            )
{
    //calculate normal of triangle
    ngl::Vector normal = calculateTriangleNormal(p1, p2, p3);
    normal.normalize();

    //calculate force of wind in direction of the triangle normal
    ngl::Vector d = _direction;

    ngl::Vector force(0, 0, 0);
    if (d.length() == 0)
    {
        //no wind
    }
    else
    {
        d.normalize();
        force = m_windStrength * normal * normal.dot(d);
    }

    //add wind force to 3 points of the triangle
    p1->addToNetForce(force);
    p2->addToNetForce(force);
    p3->addToNetForce(force);
}

ngl::Vector Cloth::calculateTriangleNormal
                            (
                                Particle* p1,
                                Particle* p2,
                                Particle* p3
                            )
{
    ngl::Vector A = p1->getPosition() - p2->getPosition();
    ngl::Vector B = p3->getPosition() - p2->getPosition();

    return A.cross(B);
}

float Cloth::determineInternalSpringForce(Spring* o_spring)
{
    //get vector from current to neighbour
    ngl::Vector AB = o_spring->getRootNode()->getPosition() - o_spring->getEndNode()->getPosition();
    float L = AB.length();

    //get relative velocity between 2 particles of the spring
    ngl::Vector V = o_spring->getRootNode()->getVelocity() - o_spring->getEndNode()->getVelocity();

    //calculate hooke's force on spring
    AB.normalize();
    ngl::Vector force_spring = -(m_stiffness * (L - o_spring->getRestLength()) * AB);
    ngl::Vector force_damping = -(m_damping * V);

    if (force_damping.length() > 0.8)
    {
        force_damping.normalize();
        force_damping = 0.8 * force_damping;
    }

    ngl::Vector netForce = force_spring + force_damping;

    //apply to both particles
    o_spring->getRootNode()->addToNetForce(netForce);
    o_spring->getEndNode()->addToNetForce(-netForce);

    return force_damping.length();
}

void Cloth::evaluateEularExplicit(Particle*currentParticle)
{
    //use Newton's 2nd law F = ma => get accelation a = F / m
    ngl::Vector acceleration = currentParticle->getNetForce() / currentParticle->getMass();

    //use a = d(v) / dt => integrate to get velocity v
    currentParticle->setVelocity(currentParticle->getVelocity() + (acceleration * m_timestep));

    //use v = d(s) / dt => integrate to get displacement s
    currentParticle->updatePosition(currentParticle->getPosition() + (currentParticle->getVelocity() * m_timestep));
}

void Cloth::evaluateVerlet(Particle* currentParticle)
{
    //use Newton's 2nd law F = ma => get accelation a = F / m
    ngl::Vector acceleration = currentParticle->getNetForce() / currentParticle->getMass();

    //verlet integration to calculate displacement froma acceleration directly
    ngl::Vector nextPosition = (2 * currentParticle->getPosition()) - currentParticle->getLastPosition() + (acceleration * m_timestep * m_timestep);

    //derive velocity
    ngl::Vector velocity = (nextPosition - currentParticle->getLastPosition()) / (2 * m_timestep);
    currentParticle->setVelocity(velocity);

    currentParticle->updatePosition(nextPosition);
}

bool Cloth::isCollisionDetected
                        (
                            ObstacleManager *_obstacleManager,
                            Particle* _currentParticle,
                            ngl::Vector &o_newPosition
                        )
{
    bool isCollided = false;

    //check collision of current particle with obstacles
    isCollided = _obstacleManager->checkAndResolveCollision(_currentParticle->getPosition(), _currentParticle->getLastPosition(), 0.1, o_newPosition);

    return isCollided;
}

int Cloth::getIdFromCoordinates(int z, int x)
{
    return (z * m_horizontalParticleCount) + x;
}

void Cloth::getZXCoordinatesFromId(int id, int &z, int &x)
{
    z = id / m_horizontalParticleCount;
    x = id % m_horizontalParticleCount;
}

void Cloth::createTextures()
{
    //creating texture objects and returning handlers
    glGenTextures(m_textureCount - 1, &m_textureHandler[0]);

    ClothMaterial* currentMaterial = 0;
    for (int i = 0; i < m_textureCount; i++)
    {
        //get cloth material
        currentMaterial = (*m_clothMaterialList)[i];

        //load texture into the texture object
        glBindTexture(GL_TEXTURE_2D, m_textureHandler[i]);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

        glTexImage2D(GL_TEXTURE_2D, 0, 4, m_textureWidth , m_textureHeight, 0, GL_RGBA,GL_UNSIGNED_BYTE, loadimgdata(currentMaterial->getTextureName()));
    }
}

unsigned char * Cloth::loadimgdata(std::string filename)
{
    //load image and return its color as an array
    QImage *image = new QImage();
    unsigned char *data;
    bool loaded = image->load(QString(filename.c_str()));

    unsigned int index = 0;
    if(loaded)
    {
        m_textureWidth = image->width();
        m_textureHeight = image->height();

        data = new unsigned char[m_textureWidth * m_textureHeight * 4];

        QRgb color;
        for (int y=0;y<m_textureHeight;++y)
        {
            for(int x=0;x<m_textureWidth;++x)
            {
                color = image->pixel(x,y);

                data[index++] =qRed(color);
                data[index++] =qGreen(color);
                data[index++] =qBlue(color);
                data[index++] =qAlpha(color);
            }
        }
    }

    return data;
}

ClothMaterial* Cloth::getDefaultClothMaterial()
{
    return getClothMaterialByName("Vintage", m_currentClothMaterialIndex, m_stiffness, m_damping);
}

ClothMaterial* Cloth::getClothMaterialByName
                                (
                                    const std::string _name,
                                    int &o_index,
                                    float &o_stiffness,
                                    float &o_damping
                                )
{
    ClothMaterial* item = 0;
    for (int i = 0; i < m_clothMaterialList->size(); i++)
    {
        item = (*m_clothMaterialList)[i];

        if (item->getName() == _name)
        {
            o_index = i;
            o_stiffness = item->getStiffness();
            o_damping = item->getDamping();
            return item;
        }
    }

    //not found -> return default @ index 0
    item = (*m_clothMaterialList)[0];
    o_index = 0;
    o_stiffness = item->getStiffness();
    o_damping = item->getDamping();
    return item;
}

void Cloth::pinInternalTraceParticle(const bool _toBePinned)
{
    Particle* tracedParticle = (*m_particleList)[m_internalTraceParticleId];
    tracedParticle->setMoveable(!_toBePinned);
}

void Cloth::pinAll(const bool _toBePinned)
{
    Particle* currentParticle = 0;
    for (int i = 0; i < m_particleList->size(); i++)
    {
        currentParticle = (*m_particleList)[i];

        currentParticle->setMoveable(!_toBePinned);
    }
}

void Cloth::pinRow(const bool _toBePinned)
{
    //get trace particle
    Particle* tracedParticle = (*m_particleList)[m_internalTraceParticleId];

    //get z,x coordinates from particle.id
    int z = 0; int x = 0;
    getZXCoordinatesFromId(tracedParticle->getId(), z, x);

    pinRow(_toBePinned, z);
}

void Cloth::pinRow
            (
                const bool _toBePinned,
                const int _row
            )
{
    Particle* currentParticle = 0;
    for (int i = 0; i < m_horizontalParticleCount; i++)
    {
        //get id from row indices
        int id = getIdFromCoordinates(_row, i);

        //get particles in that row
        currentParticle = (*m_particleList)[id];

        //set pin info
        currentParticle->setMoveable(!_toBePinned);
    }
}

void Cloth::pinColumn(const bool _toBePinned)
{
    //get trace particle
    Particle* tracedParticle = (*m_particleList)[m_internalTraceParticleId];

    //get z,x coordinates from particle.id
    int z = 0; int x = 0;
    getZXCoordinatesFromId(tracedParticle->getId(), z, x);

    pinColumn(_toBePinned, x);
}

void Cloth::pinColumn
            (
                const bool _toBePinned,
                const int _column
            )
{
    Particle* currentParticle = 0;
    for (int i = 0; i < m_verticalParticleCount; i++)
    {
        //get id from col indices
        int id = getIdFromCoordinates(i, _column);

        //get particles in that row
        currentParticle = (*m_particleList)[id];

        //set pin info
        currentParticle->setMoveable(!_toBePinned);
    }
}
